#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
文章AI处理模块
负责从数据库读取已下载的文章，调用AI服务生成摘要和标签，并更新数据库
"""
import os
import json
import re
import requests
from datetime import datetime
from readlater.database.db_manager import DatabaseManager
from readlater.config.config_manager import ConfigManager
from readlater.utils.logger import get_logger


class ArticleProcessor:
    """文章AI处理类"""

    def __init__(self):
        """初始化方法"""
        # 初始化日志记录器
        self.logger = get_logger(__name__)
        # 加载配置
        self.config = ConfigManager()
        # 初始化数据库管理器
        self.db_manager = DatabaseManager(self.config.get_database_path())
        # 获取AI API配置
        self.siliconflow_config = self.config.get("siliconflow")
        # 获取文章存储路径
        self.articles_dir = self.config.get("database.save_path")

    def get_articles_to_process(self):
        """
        从数据库中获取状态为CONTENT_DOWNLOADED的文章

        Returns:
            list: 文章列表，每篇文章包含id、url、title等信息
        """
        try:
            # 连接数据库
            if not self.db_manager.connect():
                self.logger.error("数据库连接失败，无法获取文章")
                return []

            articles = self.db_manager.get_articles_by_status(
                DatabaseManager.STATUS_CONTENT_DOWNLOADED)

            # 将结果转换为字典列表
            self.logger.info(f"成功获取 {len(articles)} 篇待处理文章")
            return articles

        except Exception as e:
            self.logger.error(f"获取待处理文章时发生错误: {str(e)}")
            return []
        finally:
            # 关闭数据库连接
            self.db_manager.close()

    def read_article_content(self, file_path):
        """
        读取文章本地文件内容

        Args:
            file_path: 文章文件路径

        Returns:
            str: 文章内容，如果读取失败则返回空字符串
        """
        try:
            full_path = os.path.join(self.config.get_save_path(), file_path)
            with open(full_path, 'r', encoding='utf-8') as f:
                content = f.read()
            self.logger.debug(f"成功读取文章文件: {full_path}")
            return content
        except Exception as e:
            self.logger.error(f"读取文章文件失败: {str(e)}, 文件路径: {file_path}")
            return ""

    def generate_summary_and_tags(self, title, content, timeout=120):
        """
        调用SiliconFlow API生成文章摘要和标签

        Args:
            title: 文章标题
            content: 文章内容

        Returns:
            tuple: (summary, tags)，摘要和标签列表
        """
        try:
            api_key = self.config.get_siliconflow_api_key()
            model = self.config.get_siliconflow_model()
            max_tokens = self.siliconflow_config.get('max_tokens', 8192)
            temperature = self.siliconflow_config.get('temperature', 0.7)

            if not api_key:
                self.logger.error("SiliconFlow API密钥未配置")
                return "", []

            # 准备请求数据
            headers = {
                'Content-Type': 'application/json',
                'Authorization': f'Bearer {api_key}'
            }

            # 构建提示词
            system_prompt = """
You are a professional Knowledge Management Specialist proficient in multi-domain content processing, including product, development, AI, daily life, and management. Your core task is to process articles from these fields and generate two key outputs in strict JSON format (all content in Chinese):

1. Summary: Concise and accurate, capturing the article’s core ideas, key arguments, and critical details. It should help users quickly grasp the main content, with a word count strictly controlled between 100-200 words (avoid overly lengthy or incomplete descriptions).
2. Tags: 5-8 relevant tags that reflect the article’s core themes, key concepts, or main categories. Follow these rules for tags:
  - Length: Each tag is 1-6 Chinese characters (concise and easy to classify).
  - Granularity: Do not over-classify (e.g., use "AI 应用" instead of "AI 图像生成应用", "产品管理" instead of "产品需求文档管理").
  - Relevance: Prioritize tags corresponding to the 5 core fields (product/development/AI/daily life/management) first, then supplement with secondary core concepts.

Ensure the JSON structure is 100% valid (no syntax errors like missing commas or quotes) with clear keys: "summary" (string) and "tags" (array of strings).
"""
            user_prompt = f"""
请基于文章标题和内容，生成符合知识管理需求的摘要与标签，具体要求如下：
1. 摘要要求
- 字数：100-200 字，简洁明了，完整覆盖文章核心观点、关键信息（如产品特性、开发步骤、AI 技术要点、生活建议、管理方法等），无冗余内容。
- 适配场景：帮助快速判断文章价值，无需通读原文即可了解核心内容。
2. 标签要求
- 数量：5-8 个，覆盖文章主要领域与核心主题；
- 分类粒度：不过细（优先使用 “产品”“开发”“AI”“生活”“管理” 等一级领域标签，或 “产品设计”“Python 开发”“AI 大模型”“居家技巧”“团队管理” 等二级标签，避免三级及以上细分标签）；
- 格式：每个标签 1-6 个中文字符，无特殊符号。

## 文章内容
文章标题: {title}
文章内容: {content}

## 输出格式
请严格按照以下 JSON 格式输出（不得添加任何额外文本，确保可直接解析）：
{{
"summary": "[此处填写 100-200 字的中文摘要]",
"tags": ["标签 1", "标签 2", "标签 3", "标签 4", "标签 5", "标签 6"（可选）, "标签 7"（可选）, "标签 8"（可选）]
}}
            """

            data = {
                'model': model,
                'messages': [
                    {
                        "role": "system",
                        "content": system_prompt
                    },
                    {
                        'role': 'user',
                        'content': user_prompt
                    }],
                'max_tokens': max_tokens,
                'temperature': temperature,
                'response_format': {"type": "json_object"}
            }

            # 调用API
            response = requests.post(
                'https://api.siliconflow.cn/v1/chat/completions',
                headers=headers,
                json=data,
                timeout=timeout
            )

            if response.status_code != 200:
                self.logger.error(
                    f"调用SiliconFlow API失败: {response.status_code}, {response.text}")
                return "", []

            # 解析响应
            result = response.json()
            if 'choices' not in result or len(result['choices']) == 0:
                self.logger.error("SiliconFlow API返回格式异常")
                return "", []

            content = result['choices'][0]['message']['content']
            self.logger.debug(f"AI生成结果: {content}")

            # 提取摘要和标签
            try:
                # 尝试使用正则表达式提取JSON内容
                json_match = re.search(r'\{.*\}', content, re.DOTALL)
                if json_match:
                    json_str = json_match.group()
                    json_content = json.loads(json_str)
                    summary = json_content.get('summary', '')
                    tags = json_content.get('tags', [])
                else:
                    # 如果没有找到JSON结构，尝试直接解析
                    json_content = json.loads(content)
                    summary = json_content['summary']
                    tags = json_content['tags']
            except json.JSONDecodeError:
                self.logger.error("AI生成结果不是有效的JSON格式")
                return content, []

            return summary, tags

        except Exception as e:
            self.logger.error(f"生成摘要和标签时发生错误: {str(e)}")
            return "", []

    def update_article_with_summary(self, article_id, summary, tags):
        """
        更新数据库中的文章摘要和标签

        Args:
            article_id: 文章ID
            summary: 文章摘要
            tags: 标签列表

        Returns:
            bool: 更新成功返回True，失败返回False
        """
        try:
            # 连接数据库
            if not self.db_manager.connect():
                self.logger.error("数据库连接失败，无法更新文章")
                return False

            # 开始事务
            self.db_manager.begin_transaction()

            # 更新文章信息
            update_query = """
                UPDATE todo_list 
                SET summary = ?, tags = ?, status = ?, datetime = ? 
                WHERE id = ?
            """
            # 将标签列表转换为逗号分隔的字符串
            tags_str = ','.join(tags)
            current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

            self.db_manager.cursor.execute(
                update_query,
                (summary, tags_str, DatabaseManager.STATUS_PROCESSED,
                 current_time, article_id)
            )

            # 提交事务
            self.db_manager.commit()
            self.logger.info(f"成功更新文章: {article_id}")
            return True

        except Exception as e:
            self.logger.error(f"更新文章时发生错误: {str(e)}, 文章ID: {article_id}")
            # 回滚事务
            self.db_manager.rollback()
            return False
        finally:
            # 关闭数据库连接
            self.db_manager.close()

    def process_articles(self):
        """
        处理所有待处理的文章
        1. 获取待处理文章
        2. 读取文章内容
        3. 调用AI生成摘要和标签
        4. 更新数据库
        """
        # 获取待处理文章
        articles = self.get_articles_to_process()
        if not articles:
            self.logger.info("没有待处理的文章")
            return

        # 处理每篇文章
        for article in articles:
            try:
                self.logger.info(f"开始处理文章: {article['title']}")
                # 读取文章内容
                content = self.read_article_content(article['file_name'])
                if not content:
                    self.logger.warning(f"文章内容为空，跳过处理: {article['id']}")
                    # 更新状态为错误
                    self.update_article_status(
                        article['id'], DatabaseManager.STATUS_ERROR, "文章内容为空")
                    continue

                # 调用AI生成摘要和标签
                summary, tags = self.generate_summary_and_tags(
                    article['title'], content, timeout=int(self.config.get("siliconflow.timeout", default=120)))
                if not summary or not tags:
                    self.logger.warning(f"生成摘要或标签失败，跳过处理: {article}(summary={summary}, tags={tags})")
                    continue

                # 更新数据库
                if not self.update_article_with_summary(article['id'], summary, tags):
                    self.logger.warning(f"更新文章失败: {article['id']}")
                    continue

                self.logger.info(f"文章处理完成: {article['title']}")
            except Exception as e:
                self.logger.error(
                    f"处理文章时发生错误: {str(e)}, 文章ID: {article['id']}")
                # 更新状态为错误
                try:
                    self.update_article_status(
                        article['id'], DatabaseManager.STATUS_ERROR, str(e))
                except Exception as e2:
                    self.logger.error(
                        f"更新文章状态失败: {str(e2)}, 文章ID: {article['id']}")

    def update_article_status(self, article_id, status, failed_reason=None):
        """
        更新文章状态

        Args:
            article_id: 文章ID
            status: 新状态
            failed_reason: 失败原因（可选）

        Returns:
            bool: 更新成功返回True，失败返回False
        """
        try:
            # 连接数据库
            if not self.db_manager.connect():
                self.logger.error("数据库连接失败，无法更新文章状态")
                return False

            # 开始事务
            self.db_manager.begin_transaction()

            # 更新文章状态
            if status == DatabaseManager.STATUS_ERROR and failed_reason:
                update_query = """
                    UPDATE todo_list 
                    SET status = ?, failed_reason = ?, datetime = ? 
                    WHERE id = ?
                """
                current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                self.db_manager.cursor.execute(
                    update_query,
                    (status, failed_reason, current_time, article_id)
                )
            else:
                update_query = """
                    UPDATE todo_list 
                    SET status = ?, datetime = ? 
                    WHERE id = ?
                """
                current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                self.db_manager.cursor.execute(
                    update_query,
                    (status, current_time, article_id)
                )

            # 提交事务
            self.db_manager.commit()
            self.logger.info(f"成功更新文章状态: {article_id}, 状态: {status}")
            return True

        except Exception as e:
            self.logger.error(f"更新文章状态时发生错误: {str(e)}, 文章ID: {article_id}")
            # 回滚事务
            self.db_manager.rollback()
            return False
        finally:
            # 关闭数据库连接
            self.db_manager.close()


if __name__ == "__main__":
    processor = ArticleProcessor()
    processor.process_articles()
